import base64
import math
from datetime import datetime
import numpy as np
from PIL import Image,ImageEnhance
from functools import wraps
from components.VolumeInfo import ViewportTransform
from share import machine_config
from type import EnumShape
import asyncio
import os
import pydicom



class Judge:
    # 选中端点的最小距离
    min_radius_check = 30

    # 选中线段的最小距离
    min_dis_to_line_check = 10
    # 绘制矩形的时候 如果面积小于该值则被清除
    min_rect_area = 200
    # 绘制angle的时候，两个点的最小距离
    min_two_point_dis = 20


def get_current_time():
    # 获取当前时间
    current_time = datetime.now()

    # 格式化输出，精确到毫秒
    formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S.%f")
    return formatted_time


def padding_arr_to_target(np_arr, png_size, viewport_transform: ViewportTransform):
    target_cols, target_rows = png_size
    bg = np.full((target_rows, target_cols), 0)
    start_row = viewport_transform.pan_y
    end_row = start_row + np_arr.shape[0]
    start_col = viewport_transform.pan_x
    end_col = start_col + np_arr.shape[1]
    bg[start_col: end_col, start_row: end_row] = np_arr
    bg_uint8 = bg.astype(np.uint8)
    return bg_uint8


def img_to_png_base64(bytes):
    base64_str = base64.b64encode(bytes).decode('utf-8')
    return f"data:image/png;base64,{base64_str}"


def img_to_jpeg_base64(bytes):
    base64_str = base64.b64encode(bytes).decode('utf-8')
    return f"data:image/jpeg;base64,{base64_str}"


def get_two_point_dis(p1, p2):
    delta_x = p1[0] - p2[0]
    delta_y = p1[1] - p2[1]
    return math.sqrt(delta_x ** 2 + delta_y ** 2)


def compose_img(base_arr, measure_layer=None):
    if machine_config['gpu']:
        return compose_img_gpu(base_arr,measure_layer)
    else:
        return compose_img_cpu(base_arr,measure_layer)

def compose_img_cpu(base_arr, measure_layer=None):
    mix_rgba_img = Image.fromarray(base_arr, mode='L')

    if measure_layer is None:
        return mix_rgba_img
    else:
        if mix_rgba_img.mode != 'RGBA':
            mix_rgba_img = mix_rgba_img.convert('RGBA')
        mix_rgba_img = Image.alpha_composite(mix_rgba_img, measure_layer)

        mix_rgba_img = mix_rgba_img.convert('RGB')
        return mix_rgba_img


def compose_img_gpu(base_arr, measure_layer=None):
    import torch
    # 将基础图像转换为 torch tensor
    base_tensor = torch.from_numpy(base_arr).float()

    if measure_layer is None:
        # 转回 CPU 并转换为 PIL 图像
        base_arr = base_tensor.cpu().numpy().astype(np.uint8)
        return Image.fromarray(base_arr, mode='L')
    else:
        # 将 measure_layer 转换为 tensor
        measure_array = np.array(measure_layer)
        measure_tensor = torch.from_numpy(measure_array).float()

        # 确保基础图像是 RGBA 格式
        if base_tensor.dim() == 2:
            # 扩展为 RGBA
            base_tensor = base_tensor.unsqueeze(2)
            base_tensor = torch.cat([
                base_tensor,
                base_tensor,
                base_tensor,
                torch.ones_like(base_tensor) * 255
            ], dim=2)

        # 执行 alpha 合成
        # Alpha blending formula: out = alpha * foreground + (1 - alpha) * background
        alpha = measure_tensor[..., 3:4] / 255.0
        result_tensor = alpha * measure_tensor[..., :3] + (1 - alpha) * base_tensor[..., :3]

        # 转回 CPU 并转换为 numpy array
        result_array = result_tensor.cpu().numpy().astype(np.uint8)

        # 创建最终的 PIL 图像
        return Image.fromarray(result_array, mode='RGB')

def calc_point_to_line_distance(c, a, b):
    ab = (b[0] - a[0], b[1] - a[1])
    ac = (c[0] - a[0], c[1] - a[1])
    if ab == (0, 0) or ac == (0, 0):
        return 0
    cross_product = ab[0] * ac[1] - ab[1] * ac[0]
    distance = abs(cross_product) / math.sqrt(ab[0] ** 2 + ab[1] ** 2)
    return distance


def calc_vector_angle(actual_vector1, actual_vector2):
    dot_product = np.dot(actual_vector1, actual_vector2)
    norm1 = np.linalg.norm(actual_vector1)
    norm2 = np.linalg.norm(actual_vector2)
    if norm1 * norm2 == 0:
        return None
    cosine_angle = dot_product / (norm1 * norm2)
    angle = np.arccos(cosine_angle)
    angle_degree = np.degrees(angle)
    return round(angle_degree, 2)


def check_is_error_draw(measure):
    measure_type = measure.measure_type
    ans = False
    reason = None
    if len(measure.points) != measure.target_points_count:
        ans = True
        reason = '缺少构成测量的点数'
        return ans, reason
    match measure_type:
        case EnumShape.rect.value | EnumShape.ellipse.value:
            p0 = measure.points[0]
            p1 = measure.points[1]
            p2 = measure.points[2]
            w = abs(p0[0] - p1[0])
            h = abs(p0[1] - p2[1])
            area = w * h
            if area < Judge.min_rect_area:
                reason = '低于绘制rect最小的面积要求'
                ans = True
            else:
                ans = False
        case EnumShape.angle.value:
            p0 = measure.points[0]
            p1 = measure.points[1]
            p2 = measure.points[2]
            if get_two_point_dis(p0, p1) <= Judge.min_two_point_dis or get_two_point_dis(p1,
                                                                                         p2) <= Judge.min_two_point_dis:
                reason = '绘制angle中两点的距离太近'
                ans = True
            else:
                ans = False
        case EnumShape.line.value:
            p0 = measure.points[0]
            p1 = measure.points[1]
            if get_two_point_dis(p0, p1) <= Judge.min_two_point_dis:
                reason = '绘制line的两点距离太近'
                ans = True
            else:
                ans = False
    return ans, reason


class HitCheck:
    def __init__(self):
        pass

    @staticmethod
    def hit_line(position, measure_type, points):
        x, y = position
        if measure_type == 'line':
            distance = calc_point_to_line_distance(position, points[0], points[1])
            if distance <= Judge.min_dis_to_line_check:
                return True
            return False
        elif measure_type == 'angle':
            distance1 = calc_point_to_line_distance(position, points[0], points[1])
            distance2 = calc_point_to_line_distance(position, points[1], points[2])
            if distance1 <= Judge.min_dis_to_line_check or distance2 <= Judge.min_dis_to_line_check:
                return True
            return False
        elif measure_type == 'rectangle' or measure_type == 'ellipse':
            x1, y1 = points[0]
            x2, y2 = points[1]
            x3, y3 = points[2]
            x4, y4 = points[3]
            cross1 = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1)
            cross2 = (x - x2) * (y3 - y2) - (y - y2) * (x3 - x2)
            cross3 = (x - x3) * (y4 - y3) - (y - y3) * (x4 - x3)
            cross4 = (x - x4) * (y1 - y4) - (y - y4) * (x1 - x4)
            if (cross1 >= 0 and cross2 >= 0 and cross3 >= 0 and cross4 >= 0) or \
                    (cross1 <= 0 and cross2 <= 0 and cross3 <= 0 and cross4 <= 0):
                return True
            else:
                return False

    @staticmethod
    def hit_point(position, points):
        dragging_index = -2
        for index, each_point in enumerate(points):
            delta_x = position[0] - each_point[0]
            delta_y = position[1] - each_point[1]
            distance = math.sqrt(delta_x ** 2 + delta_y ** 2)
            if distance <= Judge.min_radius_check:
                dragging_index = index
                break
        return dragging_index


def debounce(wait):
    """
    异步防抖装饰器，使用 asyncio 实现
    :param wait: 防抖的延迟时间，单位秒
    """

    def decorator(fn):
        # 定义装饰器内部的异步包装函数
        @wraps(fn)
        async def wrapper(*args, **kwargs):
            if hasattr(wrapper, "_timer"):
                # 取消之前的定时任务
                wrapper._timer.cancel()

            # 创建一个新的定时任务，等候 `wait` 秒后调用实际的函数
            def call_fn_later():
                asyncio.create_task(fn(*args, **kwargs))

            # 安排调用 call_fn_later 函数，延迟 `wait` 秒
            wrapper._timer = asyncio.get_event_loop().call_later(wait, call_fn_later)

        return wrapper

    return decorator


def load_directory_all_dicom_files(directory):
    dicom_files = []
    for root, _, files in os.walk(directory):
        for file in files:
            if file.lower().endswith('.dcm'):
                dicom_files.append(os.path.join(root, file))
    return dicom_files


def load_and_classify_dicom_files(dicom_path):
    series_dict = {}
    slices = []
    dicom_files = load_directory_all_dicom_files(dicom_path)

    for file_path in dicom_files:
        ds = pydicom.dcmread(file_path)
        slices.append(ds)
        if hasattr(ds, 'SeriesDescription'):
            series_desc = ds.SeriesDescription
            if series_desc in series_dict:
                series_dict[series_desc].append(ds)
            else:
                series_dict[series_desc] = []
    return series_dict, slices



